﻿
/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A
 * copy of the license can be found at http://go.microsoft.com/fwlink/?LinkID=131993. If
 * you cannot locate the Microsoft Public License, please send an email to
 * mixon@microsoft.com. By using this source code in any fashion, you are agreeing to
 * be bound by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Xml.Linq;

namespace MIXOnline.Descry
{
    /*
     * This is the primary control for the Inaugural speeches visualization.  It does the following:
     *   - Contains UI for selecting and creating RegEx-based queries
     *   - Contains code to read and parse all Inaugural speeches from InfoPath XML
     *   - Hosts a TreeMap control, and loads the TreeMap based on query
     *   - Synchronizes speech data with the DHTML DOM
     */
    public partial class Page : UserControl
    {
        private string DefaultMsgQueryText = "Type a list of words or phrases (\"enclosed in quotes\") separated by pipes (|) that represent the above topic. For instance: \"encourage diplomacy\"|\"no war\"|disarmament|disarm|peace|\"peace talks\"|non-violence. You may also use regular expressions.";
        private string DefaultTitleText = "enter a topic, eg. world peace";

        // updated whenever a new TreeMap is successfully generate
        private string currentQueryName;  
        private string currentQueryString;

        // updated when the mouse is hovering over a panel
        SpeechPanelItem highlightedItem = null;
        Brush highlightPreviousColor;

        private List<DefaultQuery> DefaultQueries;  // populated on page load

        private Speech[] Speeches;
        private bool infoHidden;
        private bool infoFlipped;
        private int infoOffset = 5;
        private bool DemocratSelected;
        private bool RepublicanSelected;
        private bool FederalistSelected;
        private bool DemRepSelected;
        private bool WhigSelected;
        private bool NoAffSelected;
        private bool TreeMapGenerated;

        private double TotalMatches;
        private double DemocratMatches;
        private long RepublicanMatches;
        private long FederalistMatches;
        private long DemRepMatches;
        private long WhigMatches;
        private long NoAffMatches;

        // Hues 
        private static double hueDemocrat = 140.0 / 240.0;
        private static double hueDemocratRepublican = 210.0 / 240.0;
        private static double hueRepublican = 0.0 / 240.0;
        private static double hueFederalist = 0.0 / 240.0;
        private static double hueWhig = 25.0 / 240.0;
        private static double hueNoAffiliation = 50.0 / 240.0;

        // Saturations; Federalist party has a different saturation
        private static double saturationAll = 200.0 / 240.0;
        private static double saturationFederalist = 0.0 / 240.0;

        // Luminances
        private static double luminanceLowAll = 70.0 / 240.0;
        private static double luminanceHighAll = 170.0 / 240.0;
        private static double luminanceRangeAll = luminanceHighAll - luminanceLowAll;

        // Set these variables for each new query; hard-coded for this example
        private static long leastMatches;
        private static long mostMatches;
        private static long matchesRange;

        public Page()
        {
            InitializeComponent();

            DefaultQueries = new List<DefaultQuery>();

            this.Loaded += new RoutedEventHandler(Page_Loaded);

            BeginLoadSpeeches();

            mouseDemocrat.MouseEnter += new MouseEventHandler(mouseDemocrat_MouseEnter);
            mouseDemocrat.MouseLeave += new MouseEventHandler(mouseDemocrat_MouseLeave);
            mouseDemocrat.MouseLeftButtonUp += new MouseButtonEventHandler(mouseDemocrat_MouseLeftButtonUp);

            mouseRepublican.MouseEnter += new MouseEventHandler(mouseRepublican_MouseEnter);
            mouseRepublican.MouseLeave += new MouseEventHandler(mouseRepublican_MouseLeave);
            mouseRepublican.MouseLeftButtonUp += new MouseButtonEventHandler(mouseRepublican_MouseLeftButtonUp);

            mouseFederalist.MouseEnter += new MouseEventHandler(mouseFederalist_MouseEnter);
            mouseFederalist.MouseLeave += new MouseEventHandler(mouseFederalist_MouseLeave);
            mouseFederalist.MouseLeftButtonUp += new MouseButtonEventHandler(mouseFederalist_MouseLeftButtonUp);

            mouseDemRep.MouseEnter += new MouseEventHandler(mouseDemRep_MouseEnter);
            mouseDemRep.MouseLeave += new MouseEventHandler(mouseDemRep_MouseLeave);
            mouseDemRep.MouseLeftButtonUp += new MouseButtonEventHandler(mouseDemRep_MouseLeftButtonUp);

            mouseWhig.MouseEnter += new MouseEventHandler(mouseWhig_MouseEnter);
            mouseWhig.MouseLeave += new MouseEventHandler(mouseWhig_MouseLeave);
            mouseWhig.MouseLeftButtonUp += new MouseButtonEventHandler(mouseWhig_MouseLeftButtonUp);

            mouseNoAff.MouseEnter += new MouseEventHandler(mouseNoAff_MouseEnter);
            mouseNoAff.MouseLeave += new MouseEventHandler(mouseNoAff_MouseLeave);
            mouseNoAff.MouseLeftButtonUp += new MouseButtonEventHandler(mouseNoAff_MouseLeftButtonUp);

            TreeMapControl.MouseEnter += new MouseEventHandler(TreeMapControl_MouseEnter);
            TreeMapControl.MouseLeave += new MouseEventHandler(TreeMapControl_MouseLeave);
            TreeMapControl.MouseMove += new MouseEventHandler(TreeMapControl_MouseMove);

            showInfo.Completed += new EventHandler(showInfo_Completed);
            hideInfoTimer.Completed += new EventHandler(hideInfoTimer_Completed);

            msgTitle.GotFocus += new RoutedEventHandler(msgTitle_GotFocus);
            msgTitle.KeyUp += new KeyEventHandler(msgTitle_KeyUp);

            CreateQueryLink.Click += new RoutedEventHandler(CreateQueryLink_Click);
            ShowCreateQuery.Completed += new EventHandler(ShowCreateQuery_Completed);
            linkPickATopic.Click += new RoutedEventHandler(linkPickATopic_Click);
            ShowSelectQuery.Completed += new EventHandler(ShowSelectQuery_Completed);

            msgQuery.GotFocus += new RoutedEventHandler(msgQuery_GotFocus);
            msgQuery.KeyUp += new KeyEventHandler(msgQuery_KeyUp);

            btnSave.Click += new RoutedEventHandler(btnSave_Click);
        }
        private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            msgTitle.Text = DefaultTitleText;
            msgQuery.Text = DefaultMsgQueryText;

            DefaultQuery Q0 = new DefaultQuery()
            {
                Name = "Economic Crisis",
                QueryString = "crisis|job|recession|poor"
            };

            DefaultQuery Q1 = new DefaultQuery()
            {
                Name = "National Debt",
                QueryString = "deficit|surplus|international trade|debt|revenue|loan"
            };

            DefaultQuery Q2 = new DefaultQuery();
            Q2.Name = "Religion";
            Q2.QueryString = "His\b|heaven|God|Heaven|Him|He\b|faith|heaven|faiths|Almighty|Author|salvation|Bible|bible|divine|Divine|Being|Parent|pray";

            DefaultQuery Q3 = new DefaultQuery();
            Q3.Name = "Family";
            Q3.QueryString = "mom^|dad|mother|father|children";

            DefaultQuery Q4 = new DefaultQuery();
            Q4.Name = "War";
            Q4.QueryString = "defense|defend|war|army|navy|armed|marines|battle|naval|arms|military";

            DefaultQuery Q5 = new DefaultQuery();
            Q5.Name = "Inclusion";
            Q5.QueryString = "we|our|friend|partner|league";

            DefaultQuery Q6 = new DefaultQuery()
            {
                Name = "Defending Liberty",
                QueryString = "defend|liberty|democracy|freedom"
            };

            DefaultQueries.AddRange(new DefaultQuery[] {Q0, Q1, Q2, Q3, Q4, Q5, Q6});


            foreach (DefaultQuery q in DefaultQueries)
            {
                SelectQuery.Items.Add(q.Name);
            }

            SearchTextBox.Text = Q0.QueryString;
            SelectQuery.SelectedIndex = 0;
            msgQueryDisplay.Text = Q0.QueryString;
            SelectQuery.SelectionChanged += new SelectionChangedEventHandler(SelectQuery_SelectionChanged);
        }


        /// <summary>
        /// When the user clicks on the TreeMap, show the speech that has been clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TreeMapControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            MsgQueryCheckLostFocus();
            MsgTitleCheckLostFocus();

            var treeMapControl = (TreeMapControl)sender;
            Point pt = e.GetPosition(App.Current.RootVisual);


            var uiElements = VisualTreeHelper.FindElementsInHostCoordinates(pt, (UIElement)sender);

            var panels = from element in uiElements where element.GetType() == typeof(SpeechPanelItem) select (SpeechPanelItem)element;

            if (panels.Count() > 0)
            {
                var panel = panels.First();
                Speech speech = (Speech)panel.Item.Tag;
                ShowSpeech(speech.When.Year.ToString());
            }

        }

        /// <summary>
        /// Start an asynchronous request to get the InfoPath XML file from the folder hosting the Silverlight App
        /// </summary>
        private void BeginLoadSpeeches()
        {
            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(new System.Uri(Application.Current.Host.Source, "inaugural.xml"));
            req.BeginGetResponse(new AsyncCallback(EndLoadSpeeches), req);
        }

        /// <summary>
        /// Now parse the InfoPath file into an array of Speech objects, and generate a TreeMap for the first query
        /// </summary>
        /// <param name="asyncResult"></param>
        private void EndLoadSpeeches(IAsyncResult asyncResult)
        {
            HttpWebRequest req = (HttpWebRequest)asyncResult.AsyncState;
            HttpWebResponse resp = (HttpWebResponse)req.EndGetResponse(asyncResult);

            XDocument speechdoc = XDocument.Load(resp.GetResponseStream());

            XNamespace my = "http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-12-17T20:21:56";
            XNamespace xhtml = "http://www.w3.org/1999/xhtml";

            var speeches = from speech in speechdoc.Descendants(my + "speech")
                           select new Speech
                           {
                               President = speech.Attribute(my + "president").Value,
                               Tagline = speech.Attribute(my + "tagline").Value,
                               When = DateTime.Parse(speech.Attribute(my + "date").Value),
                               Party = speech.Attribute(my + "party").Value,
                               Info = speech.Element(my + "info").Value.Trim(),
                               Paragraphs =
                                 (from para in speech.Descendants(xhtml + "div")
                                  where para.Value.Trim().Length > 1
                                  select para.Value.Trim()).ToArray()

                           };

            Speeches = speeches.ToArray();
            this.Dispatcher.BeginInvoke(delegate()
            {
                if (GenerateTreeMap(SearchTextBox.Text))
                {
                    currentQueryName = SelectQuery.SelectedItem.ToString();
                    currentQueryString = SearchTextBox.Text;
                }
            }
            );
        }

        /// <summary>
        /// Manipulates the HTML DOM to show the specified speech
        /// </summary>
        /// <param name="year">Pass in the 4-digit year of the inauguration speech.  For example, "2009" for Barack Obama</param>
        private void ShowSpeech(string year)
        {
            Speech speech = (from s in Speeches where s.When.Year.ToString() == year select s).First();
            if (speech == null) return;

            setDOM("president", speech.President);
            setDOM("tagline", speech.Tagline);

            setDOM("date", speech.When.ToLongDateString());
            setDOM("speechinfo", speech.Info);
            setDOM("topic", currentQueryName);

            Regex r = new Regex(currentQueryString);
            setDOM("matches", (from para in speech.Paragraphs select r.Matches(para).Count).Sum().ToString());

            var paragraphs = from para in speech.Paragraphs
                             select
                                 XDocument.Parse("<p>" + Hilite(para, currentQueryString) + "</p>").Root;
            string speechBody = "";
            foreach (var para in paragraphs) speechBody += para.ToString();
            setDOM("speechbody", speechBody);
            setDOMVisible("speech", true);
            setDOMVisible("no-speech", false);
            HtmlPage.Window.CurrentBookmark = "speech";

        }

        /// <summary>
        /// Helper method to set the innerHTML on a node in the HTML DOM.  If the id is not found, returns with no error.
        /// </summary>
        /// <param name="id">The ID of the node in the HTML DOM</param>
        /// <param name="innerHTML">The innerHTML you want to set</param>
        private void setDOM(string id, string innerHTML)
        {
            try
            {
                var doc = HtmlPage.Document;
                var node = doc.GetElementById(id);
                if (node != null) node.SetProperty("innerHTML", innerHTML);
            }
            catch (Exception e) { }
        }

        /// <summary>
        /// Shows or hides an element in the HTML DOM.
        /// </summary>
        /// <param name="id">The ID of the HTML DOM node to show or hide</param>
        /// <param name="visibility">True to set visible ("display:block"), False to hide the node ("display:none")</param>
        private void setDOMVisible(string id, bool visibility)
        {
            try
            {
                var doc = HtmlPage.Document;
                var node = doc.GetElementById(id);
                if (node != null) node.SetStyleAttribute("display" , (visibility ? "block" : "none"));
            }
            catch (Exception e) { }
        }

        /// <summary>
        /// Takes a string of HTML, and highlights all of the text that matches the supplied regular expression
        /// </summary>
        /// <param name="para">The paragraph of HTML, formatted as string like you would pass as innerHTML</param>
        /// <param name="regex">The regular expression to match</param>
        /// <returns>A new string of HTML with highlight spans wrapped around all matches</returns>
        private string Hilite(string para, string regex)
        {
            Regex r = new Regex(regex);
            var mc = r.Matches(para);
            if (mc.Count == 0) return para;

            return r.Replace(para, "<span class='highlight'>$&</span>");
        }

        /// <summary>
        /// Called whenever the user attempts to generate a TreeMap for a custom query
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (msgTitle.Text.Length == 0)
            {
                HtmlPage.Window.Alert("Please enter a title and some search terms and click 'Generate'");
                return;
            }

            if (GenerateTreeMap(msgQuery.Text))
            {
                currentQueryString = msgQuery.Text;
                currentQueryName = msgTitle.Text;
            }
            else
            {
                HtmlPage.Window.Alert("No matches for " + msgQuery.Text);
            }
        }

        /// <summary>
        /// Searches all inaugiral speeches for matches, and renders the TreeMap representing matches
        /// </summary>
        /// <param name="regEx">The regular expression describing what terms to look for in each speech</param>
        /// <returns>True if there were matches and the TreeMap was generated; False if there are no matches</returns>
        private bool GenerateTreeMap(string regEx)
        {
            Regex r = new Regex(regEx);

            var partyColors = new Dictionary<string, Color>();
            partyColors.Add("Democratic", Color.FromArgb(255, 2, 77, 168));
            partyColors.Add("Republican", Color.FromArgb(255, 252, 44, 44));
            partyColors.Add("Federalist", Color.FromArgb(255, 29, 29, 29));
            partyColors.Add("Democratic-Republican", Color.FromArgb(255, 56, 6, 155));
            partyColors.Add("Whig", Color.FromArgb(255, 211, 175, 67));
            partyColors.Add("None", Color.FromArgb(255, 176, 226, 5));

            var matchCount = from speech in Speeches
                             select
                                 new
                                 {
                                     President = speech.President,
                                     Year = speech.When,
                                     Party = speech.Party,
                                     Count =
                                     (from para in speech.Paragraphs select r.Matches(para).Count).Sum(),
                                     SpeechRef = speech
                                 };


            var matchMap = from speech in matchCount
                           orderby speech.Count descending
                           where speech.Count > 0
                           select new TreeMapItem
                           {
                               Label = speech.President,
                               Size = speech.Count,
                               Color = partyColors[speech.Party],
                               Tag = speech.SpeechRef,
                               PanelItemType = typeof(SpeechPanelItem)
                           };

            if (matchMap.Count() == 0) return false;

            TreeMapControl.LoadTreeMap(matchMap);

            // calculate max and min matches to get range for luminence
            mostMatches = 1;
            leastMatches = 10000;

            foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
            {
                Speech speech = (Speech)panel.Item.Tag;
                if (panel.Item.Size > mostMatches) { mostMatches = panel.Item.Size; }
                if (panel.Item.Size < leastMatches) { leastMatches = panel.Item.Size; }
            }

            // calculate match range for luminence calculations
            matchesRange = mostMatches - leastMatches;

            foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
            {
                Speech speech = (Speech)panel.Item.Tag;

                Color c = getWeightedColor(speech.Party, panel.Item.Size);
                panel.Background = new SolidColorBrush(c);

                TotalMatches += panel.Item.Size;

                switch (speech.Party)
                {
                    case "Democratic":
                        DemocratMatches += panel.Item.Size;
                        break;
                    case "Republican":
                        RepublicanMatches += panel.Item.Size;
                        break;
                    case "Federalist":
                        FederalistMatches += panel.Item.Size;
                        break;
                    case "Democratic-Republican":
                        DemRepMatches += panel.Item.Size;
                        break;
                    case "Whig":
                        WhigMatches += panel.Item.Size;
                        break;
                    case "None":
                        NoAffMatches += panel.Item.Size;
                        break;
                }
            }

            msgDemocrat.Text = "Democrat (" + Math.Ceiling((DemocratMatches / TotalMatches) * 100.0) + "%)";
            //Color labelColorD = getWeightedColor("Democratic", leastMatches);
            //msgDemocrat.Foreground = new SolidColorBrush(labelColorD);
            msgDemocrat.Foreground = new SolidColorBrush(partyColors["Democratic"]);

            msgRepublican.Text = "Republican (" + Math.Ceiling((RepublicanMatches / TotalMatches) * 100.0) + "%)";
            //Color labelColorR = getWeightedColor("Republican", leastMatches);
            //msgRepublican.Foreground = new SolidColorBrush(labelColorR);
            msgRepublican.Foreground = new SolidColorBrush(partyColors["Republican"]);

            msgFederalist.Text = "Federalist (" + Math.Ceiling((FederalistMatches / TotalMatches) * 100.0) + "%)";
            //Color labelColorF = getWeightedColor("Federalist", leastMatches);
            //msgFederalist.Foreground = new SolidColorBrush(labelColorF);
            msgFederalist.Foreground = new SolidColorBrush(partyColors["Federalist"]);

            msgDemRep.Text = "Democratic-Republican (" + Math.Ceiling((DemRepMatches / TotalMatches) * 100.0) + "%)";
            //Color labelColorDR = getWeightedColor("Democratic-Republican", leastMatches);
            //msgDemRep.Foreground = new SolidColorBrush(labelColorDR);
            msgDemRep.Foreground = new SolidColorBrush(partyColors["Democratic-Republican"]);

            msgWhig.Text = "Whig (" + Math.Ceiling((WhigMatches / TotalMatches) * 100.0) + "%)";
            //Color labelColorW = getWeightedColor("Whig", leastMatches);
            //msgWhig.Foreground = new SolidColorBrush(labelColorW);
            msgWhig.Foreground = new SolidColorBrush(partyColors["Whig"]);

            msgNoAff.Text = "No Affiliation (" + Math.Ceiling((NoAffMatches / TotalMatches) * 100.0) + "%)";
            //Color labelColorN = getWeightedColor("None", leastMatches);
            //msgNoAff.Foreground = new SolidColorBrush(labelColorN);
            msgNoAff.Foreground = new SolidColorBrush(partyColors["None"]);

            setDOMVisible("speech", false);
            setDOMVisible("no-speech", true);

            Dispatcher.BeginInvoke(delegate() { AdjustPanels(); });

            return true;

        }

        /// <summary>
        /// Adjust label sizes on TreeMap panels to indicate magnitude of the quantity
        /// </summary>
        /// <remarks>
        /// After the TreeMap is generated, we need to allow the asynchronous layout pass to finish before we adjust label sizes.  This routine is invoked by dispatcher.BeginInvoke from GenerateTreeMap.
        /// </remarks>
        private void AdjustPanels()
        {
            // do not display any panel with 0 matches
            foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
            {
                if (panel.Item.Size == 0)
                {
                    panel.Visibility = Visibility.Collapsed;
                }
            }

            foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
            {
                panel.paneContentPanel.Width = panel.ActualWidth;
                panel.paneContentPanel.Height = panel.ActualHeight;

                double paneWidth = panel.ActualWidth * .85;
                double paneHeight = panel.ActualHeight * .85;

                if (paneWidth != 0)
                {
                    try
                    {
                        panel.whenBlock.Width = paneWidth - 10;
                        panel.textBlock.Width = paneWidth - 10;
                        panel.textBlockShadow.Width = paneWidth - 10;

                        if (panel.whenBlock.Width < 0) { panel.whenBlock.Width = 0; }
                        if (panel.textBlock.Width < 0)
                        {
                            panel.textBlock.Width = 0;
                            panel.textBlockShadow.Width = 0;
                        }
                    }
                    catch
                    {
                        // zero or NaN sized element encountered - do nothing
                    }
                }

                // adjust the font size of the date label in each pane
                if (paneWidth < 95) { panel.whenBlock.FontSize = 5; }
                else if (paneWidth > 95 && paneWidth < 150) { panel.whenBlock.FontSize = 10; }
                else if (paneWidth > 150 && paneWidth < 175) { panel.whenBlock.FontSize = 14; }
                else if (paneWidth > 175 && paneWidth < 200) { panel.whenBlock.FontSize = 16; }
                else if (paneWidth > 200) { panel.whenBlock.FontSize = 20; }

                // set the location of the label text block
                Canvas.SetTop(panel.textBlock, panel.whenBlock.ActualHeight + 5);
                Canvas.SetTop(panel.textBlockShadow, Canvas.GetTop(panel.textBlock) - 1);

                // adjust the font size of the label text block
                if (paneWidth < 55)
                {
                    panel.textBlock.FontSize = 6;
                    panel.textBlockShadow.FontSize = 6;
                }

                else if (paneWidth > 54 && paneWidth < 95)
                {
                    panel.textBlock.FontSize = 12;
                    panel.textBlockShadow.FontSize = 12;
                }
                else if (paneWidth > 95 && paneWidth < 150)
                {
                    panel.textBlock.FontSize = 16;
                    panel.textBlockShadow.FontSize = 16;
                }
                else if (paneWidth > 150 && paneWidth < 175)
                {
                    panel.textBlock.FontSize = 20;
                    panel.textBlockShadow.FontSize = 20;
                }

                else if (paneWidth > 175 && paneWidth < 200)
                {
                    panel.textBlock.FontSize = 26;
                    panel.textBlockShadow.FontSize = 26;
                }

                else if (paneWidth > 200)
                {
                    panel.textBlock.FontSize = 34;
                    panel.textBlockShadow.FontSize = 34;
                }

                // suppresses the display of the "matches" circle on
                // panels that are < 75 pixels wide
                if (paneWidth > 45)
                {
                    Ellipse ell = new Ellipse()
                    {
                        Fill = new SolidColorBrush(Colors.White),
                        Opacity = .25,
                    };

                    TextBlock msgMatches = new TextBlock()
                    {
                        Foreground = new SolidColorBrush(Colors.White)
                    };

                    panel.paneContentPanel.Children.Add(ell);
                    panel.paneContentPanel.Children.Add(msgMatches);

                    msgMatches.FontSize = panel.whenBlock.FontSize;
                    msgMatches.Text = panel.Item.Size.ToString();

                    ell.Width = ell.Height = paneWidth * .25;

                    Canvas.SetLeft(ell, panel.ActualWidth - (ell.Width + 16));
                    Canvas.SetTop(ell, panel.ActualHeight - (ell.Height + 13));

                    double EllCenterX = Canvas.GetLeft(ell) + (ell.Width / 2);
                    double EllCenterY = Canvas.GetTop(ell) + (ell.Height / 2);

                    Canvas.SetLeft(msgMatches, EllCenterX - (.5 * msgMatches.ActualWidth));
                    Canvas.SetTop(msgMatches, EllCenterY - (.5 * msgMatches.ActualHeight));
                }
            }

            TreeMapGenerated = true;
            displaySelectedParty();
            TreeMapControl.Visibility = Visibility.Visible;

        }

        /// <summary>
        /// Gets background color for a cell based on corresponding 
        /// value of total matches
        /// </summary>
        /// <param name="party">Enum of the target party (used to derive hue)</param>
        /// <param name="numberOfMatches">Total matches for the speech in question</param>
        /// <returns>ARGB color</returns>
        public Color getWeightedColor(string party, long numberOfMatches)
        {
            // Accommodate out of bounds
            if (numberOfMatches > mostMatches)
                numberOfMatches = mostMatches;
            else if (numberOfMatches < leastMatches || numberOfMatches < 0)
                numberOfMatches = leastMatches;

            // set target luminance to default (i.e. minimum luminance)
            double targetLuminance = luminanceHighAll;

            // normalize
            targetLuminance -= (luminanceRangeAll * (numberOfMatches - leastMatches)) / matchesRange;

            // calculate color of target panel
            switch (party)
            {
                case "Democratic":
                    return HSL2RGB(hueDemocrat, targetLuminance, saturationAll);

                case "Democratic-Republican":
                    return HSL2RGB(hueDemocratRepublican, targetLuminance, saturationAll);

                case "Federalist":
                    return HSL2RGB(hueFederalist, targetLuminance, saturationFederalist);

                case "Republican":
                    return HSL2RGB(hueRepublican, targetLuminance, saturationAll);

                case "Whig":
                    return HSL2RGB(hueWhig, targetLuminance, saturationAll);

                case "None":
                    return HSL2RGB(hueNoAffiliation, targetLuminance, saturationAll);
            }

            return Color.FromArgb(255, 0, 0, 0);

        }

        /// <summary>
        /// Converts from HLS color space to RGB
        /// Adapted from: http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
        /// </summary>
        /// <param name="h">hue</param>
        /// <param name="l">luminance</param>
        /// <param name="sl">saturation</param>
        /// <returns>ARGB equivalent for HLS value</returns>
        public Color HSL2RGB(double h, double l, double sl)
        {
            double v;
            double r, g, b;

            r = l;
            g = l;
            b = l;
            v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl);

            if (v > 0)
            {
                double m;
                double sv;
                int sextant;
                double fract, vsf, mid1, mid2;

                m = l + l - v;
                sv = (v - m) / v;
                h *= 6.0;

                sextant = (int)h;
                fract = h - sextant;
                vsf = v * sv * fract;
                mid1 = m + vsf;
                mid2 = v - vsf;

                switch (sextant)
                {
                    case 0:
                        r = v;
                        g = mid1;
                        b = m;
                        break;

                    case 1:
                        r = mid2;
                        g = v;
                        b = m;
                        break;

                    case 2:
                        r = m;
                        g = v;
                        b = mid1;
                        break;

                    case 3:
                        r = m;
                        g = mid2;
                        b = v;
                        break;

                    case 4:
                        r = mid1;
                        g = m;
                        b = v;
                        break;

                    case 5:
                        r = v;
                        g = m;
                        b = mid2;
                        break;
                }
            }

            return Color.FromArgb((byte)255.0, (byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
        }

        /// <summary>
        /// When the selection changes, we need to re-generate the TreeMap and hide the speech at the bottom
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SelectQuery_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            string SelectedQuery = SelectQuery.SelectedItem.ToString();

            SearchTextBox.Text = SelectedQuery;

            foreach (DefaultQuery q in DefaultQueries)
            {
                if (q.Name == SelectedQuery)
                {
                    msgQueryDisplay.Text = q.QueryString;
                }
            }


            if (GenerateTreeMap(msgQueryDisplay.Text))
            {
                currentQueryName = SelectedQuery;
                currentQueryString = msgQueryDisplay.Text;
            }
        }


        /// <summary>
        /// Sets the opacity such that the selected party stands out from the rest
        /// </summary>
        private void displaySelectedParty()
        {
            MsgQueryCheckLostFocus();
            MsgTitleCheckLostFocus();
            string whichPartySelected = "";

            if (DemocratSelected) { whichPartySelected = "Democratic"; }
            else if (RepublicanSelected) { whichPartySelected = "Republican"; }
            else if (FederalistSelected) { whichPartySelected = "Federalist"; }
            else if (DemRepSelected) { whichPartySelected = "Democratic-Republican"; }
            else if (WhigSelected) { whichPartySelected = "Whig"; }
            else if (NoAffSelected) { whichPartySelected = "None"; }
            else { return; }

            foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
            {
                Speech speech = (Speech)panel.Item.Tag;
                if (speech.Party != whichPartySelected)
                {
                    panel.Opacity = .25;
                }
            }
        }


        /// <summary>
        /// Resets the opacity of all parties to appear the same
        /// </summary>
        private void ClearAllHighlights()
        {
            // clear all legend highlights and flags
            mouseDemocrat.Opacity = 0;
            DemocratSelected = false;
            mouseRepublican.Opacity = 0;
            RepublicanSelected = false;
            mouseFederalist.Opacity = 0;
            FederalistSelected = false;
            mouseDemRep.Opacity = 0;
            DemRepSelected = false;
            mouseWhig.Opacity = 0;
            WhigSelected = false;
            mouseNoAff.Opacity = 0;
            NoAffSelected = false;

            // reset all pane opacity
            foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
            {
                panel.Opacity = 1;
            }
        }

        /// <summary>
        /// Called when the user clicks "save" on a created query
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSave_Click(object sender, RoutedEventArgs e)
        {
            bool QueryExists = false;

            string CheckTitleExists = msgTitle.Text;
            foreach (DefaultQuery q in DefaultQueries)
            {
                if (q.Name == CheckTitleExists)
                {
                    HtmlPage.Window.Alert("A query with this name already exists.\nPlease enter a new name and click SAVE.");
                    QueryExists = true;
                }
            }

            string CheckQueryExists = msgQuery.Text;
            foreach (DefaultQuery q in DefaultQueries)
            {
                if (q.Name == CheckQueryExists)
                {
                    HtmlPage.Window.Alert("This query already exists.\nPlease enter a new query string and click SAVE.");
                    QueryExists = true;
                }
            }

            if (!QueryExists)
            {
                DefaultQuery newQuery = new DefaultQuery();
                newQuery.Name = msgTitle.Text;
                newQuery.QueryString = msgQuery.Text;

                DefaultQueries.Add(newQuery);
                SelectQuery.Items.Add(newQuery.Name);

                HtmlPage.Window.Alert("This query has been saved.");
            }
        }

        private void msgQuery_KeyUp(object sender, KeyEventArgs e)
        {
            msgQuery.Foreground = new SolidColorBrush(Colors.White);
            btnGenerate.IsEnabled = true;
        }

        private void msgQuery_GotFocus(object sender, RoutedEventArgs e)
        {
            if (msgTitle.Text == "" && msgQuery.Text != DefaultMsgQueryText)
            {
                msgTitle.Foreground = new SolidColorBrush(Color.FromArgb(255, 102, 102, 102));
                msgTitle.FontStyle = FontStyles.Italic;
                msgTitle.Text = DefaultTitleText;
                btnGenerate.IsEnabled = true;
                btnSave.IsEnabled = false;
            }

            if (msgQuery.Text == DefaultMsgQueryText)
            {
                msgQuery.Text = "";
            }
        }

        private void MsgQueryCheckLostFocus()
        {
            if (msgQuery.Text == "")
            {
                msgQuery.Foreground = new SolidColorBrush(Color.FromArgb(255, 102, 102, 102));
                msgQuery.Text = DefaultMsgQueryText;
                btnGenerate.IsEnabled = false;
                btnSave.IsEnabled = false;
            }
            TakeFocus.Focus();
        }

        private void MsgTitleCheckLostFocus()
        {
            if (msgTitle.Text == "")
            {
                msgTitle.Foreground = new SolidColorBrush(Color.FromArgb(255, 102, 102, 102));
                msgTitle.FontStyle = FontStyles.Italic;
                msgTitle.Text = DefaultTitleText;
                btnGenerate.IsEnabled = false;
                btnSave.IsEnabled = false;
            }
            TakeFocus.Focus();
        }

        private void msgTitle_GotFocus(object sender, RoutedEventArgs e)
        {
            if (msgQuery.Text == "")
            {
                msgQuery.Foreground = new SolidColorBrush(Color.FromArgb(255, 102, 102, 102));
                msgQuery.Text = DefaultMsgQueryText;
                btnGenerate.IsEnabled = false;
                btnSave.IsEnabled = false;
            }

            if (msgTitle.Text == DefaultTitleText)
            {
                msgTitle.Text = "";
            }
        }

        private void msgTitle_KeyUp(object sender, KeyEventArgs e)
        {
            msgTitle.Foreground = new SolidColorBrush(Colors.White);
            msgTitle.FontStyle = FontStyles.Normal;

            if (msgTitle.Text != DefaultTitleText && msgQuery.Text != DefaultMsgQueryText)
            {
                btnGenerate.IsEnabled = true;
                btnSave.IsEnabled = true;
            }
        }

        private void ShowSelectQuery_Completed(object sender, EventArgs e)
        {
            SelectView.IsHitTestVisible = true;
        }

        private void linkPickATopic_Click(object sender, RoutedEventArgs e)
        {
            QueryView.IsHitTestVisible = SelectView.IsHitTestVisible = false;
            ShowSelectQuery.Begin();
        }

        private void ShowCreateQuery_Completed(object sender, EventArgs e)
        {
            QueryView.IsHitTestVisible = true;
        }

        private void CreateQueryLink_Click(object sender, RoutedEventArgs e)
        {
            msgQuery.Foreground = new SolidColorBrush(Color.FromArgb(255, 102, 102, 102));
            msgQuery.Text = DefaultMsgQueryText;
            msgTitle.Foreground = new SolidColorBrush(Color.FromArgb(255, 102, 102, 102));
            msgTitle.Text = DefaultTitleText;
            btnGenerate.IsEnabled = false;
            btnSave.IsEnabled = false;

            QueryView.IsHitTestVisible = SelectView.IsHitTestVisible = false;
            ShowCreateQuery.Begin();
        }


        #region info pane timers/events
        private void hideInfoTimer_Completed(object sender, EventArgs e)
        {
            hideInfo.Begin();
            infoHidden = true;
        }

        private void showInfo_Completed(object sender, EventArgs e)
        {
            infoHidden = false;
            hideInfoTimer.Begin();
        }

        private void TreeMapControl_MouseLeave(object sender, MouseEventArgs e)
        {
            hideInfo.Begin();

            if (highlightedItem != null)
            {
                highlightedItem.Background = highlightPreviousColor;
                highlightedItem = null;
            }

        }

        private void TreeMapControl_MouseEnter(object sender, MouseEventArgs e)
        {
            if (TreeMapGenerated)
            {
                var treeMapControl = (TreeMapControl)sender;
                Point pt = e.GetPosition(App.Current.RootVisual);

                movePopup(pt);

                showInfo.Begin();
            }
        }

        private void TreeMapControl_MouseMove(object sender, MouseEventArgs e)
        {
            showInfo.Begin();

            var treeMapControl = (TreeMapControl)sender;
            Point pt = e.GetPosition(App.Current.RootVisual);

            movePopup(pt);

            var uiElements = VisualTreeHelper.FindElementsInHostCoordinates(pt, (UIElement)sender);
            var panels = from element in uiElements where element.GetType() == typeof(SpeechPanelItem) select (SpeechPanelItem)element;


            var partyTextColors = new Dictionary<string, Color>();
            partyTextColors.Add("Democratic", Color.FromArgb(255, 2, 77, 168));
            partyTextColors.Add("Republican", Color.FromArgb(255, 252, 44, 44));
            partyTextColors.Add("Federalist", Color.FromArgb(255, 29, 29, 29));
            partyTextColors.Add("Democratic-Republican", Color.FromArgb(255, 56, 6, 155));
            partyTextColors.Add("Whig", Color.FromArgb(255, 211, 175, 67));
            partyTextColors.Add("None", Color.FromArgb(255, 176, 226, 5));

            if (panels.Count() > 0)
            {
                var panel = panels.First();

                TreeMapItem item = panel.Item;
                Speech speech = (Speech)item.Tag;

                msgPresident.Foreground =
                    msgYear.Foreground =
                    msgMatches.Foreground =
                    msgView.Foreground =
                    new SolidColorBrush(partyTextColors[speech.Party]);


                // Highlight the item
                if (highlightedItem != panel)
                {
                    // This panel isn't highlighted yet.  Need to remove highlight on old panel, then highlight this one.
                    if (highlightedItem != null)
                    {
                        highlightedItem.Background = highlightPreviousColor;
                    }

                    highlightPreviousColor = panel.Background;
                    highlightedItem = panel;

                    // this is just ugly; you can change luminence or do something else here, too
                    
                    panel.Background = new SolidColorBrush(Color.FromArgb (255, 51, 51, 51));
                }
                panel.Background = new SolidColorBrush(Color.FromArgb(255, 51, 51, 51));

                msgPresident.Text = panel.Item.Label;
                msgYear.Text = speech.Tagline + " delivered on " + speech.When.ToString("D") + ".";
                msgMatches.Text = panel.Item.Size + " matches.";
                msgView.Text = "Click to view.";
            }

            stackPanel.Visibility = Visibility.Visible;

            if (!infoHidden) { hideInfoTimer.Begin(); }
            else { showInfo.Begin(); }

        }

        private void movePopup(Point popLoc)
        {
            if (infoPopup.Width > (LayoutRoot.Width - popLoc.X))
            {
                if (!infoFlipped)
                {
                    flipInfoPaneLeft.Begin();
                }
                infoOffset = -5;
                infoPopup.Margin = new Thickness(popLoc.X + infoOffset, popLoc.Y - (infoPopup.Height / 2), 0, 0);
                infoFlipped = true;
            }

            else if (popLoc.X < infoPopup.Width)
            {
                if (infoFlipped)
                {
                    flipInfoPaneRight.Begin();
                }
                infoOffset = 5;
                infoPopup.Margin = new Thickness(popLoc.X + infoOffset, popLoc.Y - (infoPopup.Height / 2), 0, 0);
                infoFlipped = false;
            }
            else
            {
                infoPopup.Margin = new Thickness(popLoc.X + infoOffset, popLoc.Y - (infoPopup.Height / 2), 0, 0);
            }
        }
        #endregion info pane timers/events

        #region no affiliation legend mouse events
        private void mouseNoAff_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!NoAffSelected)
            {
                mouseNoAff.Opacity = 0;
            }
        }

        private void mouseNoAff_MouseEnter(object sender, MouseEventArgs e)
        {
            mouseNoAff.Opacity = 1;
        }

        private void mouseNoAff_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (DemocratSelected || RepublicanSelected || FederalistSelected || DemRepSelected || WhigSelected)
            {
                ClearAllHighlights();
            }

            if (!NoAffSelected)
            {
                foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
                {
                    NoAffSelected = true;
                    mouseNoAff.Opacity = 1;
                    displaySelectedParty();
                }
            }
            else
            {
                ClearAllHighlights();
            }
        }
        #endregion no affiliation legend mouse events

        #region whig legend mouse events
        private void mouseWhig_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!WhigSelected)
            {
                mouseWhig.Opacity = 0;
            }
        }

        private void mouseWhig_MouseEnter(object sender, MouseEventArgs e)
        {
            mouseWhig.Opacity = 1;
        }

        private void mouseWhig_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (DemocratSelected || RepublicanSelected || FederalistSelected || DemRepSelected || NoAffSelected)
            {
                ClearAllHighlights();
            }

            if (!WhigSelected)
            {
                foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
                {
                    WhigSelected = true;
                    mouseWhig.Opacity = 1;
                    displaySelectedParty();
                }
            }
            else
            {
                ClearAllHighlights();
            }
        }
        #endregion whig legend mouse events

        #region democratic republican legend mouse events
        private void mouseDemRep_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!DemRepSelected)
            {
                mouseDemRep.Opacity = 0;
            }
        }

        private void mouseDemRep_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (DemocratSelected || RepublicanSelected || FederalistSelected || WhigSelected || NoAffSelected)
            {
                ClearAllHighlights();
            }

            if (!DemRepSelected)
            {
                foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
                {
                    DemRepSelected = true;
                    mouseDemRep.Opacity = 1;
                    displaySelectedParty();
                }
            }
            else
            {
                ClearAllHighlights();
            }
        }

        private void mouseDemRep_MouseEnter(object sender, MouseEventArgs e)
        {
            mouseDemRep.Opacity = 1;
        }
        #endregion democratic republican legend mouse events

        #region federalist legend mouse events
        private void mouseFederalist_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!FederalistSelected)
            {
                mouseFederalist.Opacity = 0;
            }
        }

        private void mouseFederalist_MouseEnter(object sender, MouseEventArgs e)
        {
            mouseFederalist.Opacity = 1;
        }

        private void mouseFederalist_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (DemocratSelected || RepublicanSelected || DemRepSelected || WhigSelected || NoAffSelected)
            {
                ClearAllHighlights();
            }

            if (!FederalistSelected)
            {
                foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
                {
                    FederalistSelected = true;
                    mouseFederalist.Opacity = 1;
                    displaySelectedParty();
                }
            }
            else
            {
                ClearAllHighlights();
            }
        }
        #endregion federalist legend mouse events

        #region republican legend mouse events
        private void mouseRepublican_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!RepublicanSelected)
            {
                mouseRepublican.Opacity = 0;
            }
        }

        private void mouseRepublican_MouseEnter(object sender, MouseEventArgs e)
        {
            mouseRepublican.Opacity = 1;
        }

        private void mouseRepublican_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (DemocratSelected || FederalistSelected || DemRepSelected || WhigSelected || NoAffSelected)
            {
                ClearAllHighlights();
            }

            if (!RepublicanSelected)
            {
                foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
                {
                    RepublicanSelected = true;
                    mouseRepublican.Opacity = 1;
                    displaySelectedParty();
                }
            }
            else
            {
                ClearAllHighlights();
            }
        }
        #endregion republican legend mouse events

        #region democrat legend mouse events
        private void mouseDemocrat_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (RepublicanSelected || FederalistSelected || DemRepSelected || WhigSelected || NoAffSelected)
            {
                ClearAllHighlights();
            }

            if (!DemocratSelected)
            {
                foreach (SpeechPanelItem panel in TreeMapControl.LeafPanels())
                {
                    DemocratSelected = true;
                    mouseDemocrat.Opacity = 1;
                    displaySelectedParty();
                }
            }
            else
            {
                ClearAllHighlights();
            }

        }

        private void mouseDemocrat_MouseLeave(object sender, MouseEventArgs e)
        {
            if (!DemocratSelected)
            {
                mouseDemocrat.Opacity = 0;
            }
        }

        private void mouseDemocrat_MouseEnter(object sender, MouseEventArgs e)
        {
            mouseDemocrat.Opacity = 1;
        }
        #endregion democrat legend mouse events


        // End Class Definition    
    }

    /// <summary>
    ///  Represents an inaugural speech
    /// </summary>
    class Speech
    {
        public DateTime When;
        public string President;
        public string Tagline;
        /// <summary>
        /// Some editorial information about each speech
        /// </summary>
        public string Info;
        public string Party;
        /// <summary>
        /// A list of the paragraphs of the speech, in sequential order
        /// </summary>
        public string[] Paragraphs;
    }

    /// <summary>
    /// A title and associated RegEx used to populate the selction list
    /// </summary>
    class DefaultQuery
    {
        public string Name;
        public string QueryString;
    }

    /// <summary>
    /// Specialized TreeMapPanelItem with extra information about the speech and extra visual controls
    /// </summary>
    class SpeechPanelItem : TreeMapPanelItem
    {
        public Canvas paneContentPanel = null;
        public TextBlock textBlock = null;
        public TextBlock textBlockShadow = null;
        public Rectangle outerStroke = null;

        public TextBlock whenBlock = null;

        public SpeechPanelItem(TreeMapItem item)
            : base(item)
        {
           

            StackPanel textPanel = new StackPanel();
            textPanel.Visibility = Visibility.Collapsed;

            paneContentPanel = new Canvas()
            {
                Margin = new Thickness(8, 5, 8, 5),
            };

            this.Children.Add(paneContentPanel);


            textBlockShadow = new TextBlock()
            {
                Opacity = .65,
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
                TextWrapping = TextWrapping.Wrap,
                FontSize = 18,
                Foreground = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)),
            };

            textBlock = new TextBlock()
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
                TextWrapping = TextWrapping.Wrap,
                FontSize = 18,
                Foreground = new SolidColorBrush(Color.FromArgb(255, 255, 255, 255)),
            };

            paneContentPanel.Children.Add(textBlockShadow);
            paneContentPanel.Children.Add(textBlock);

            this.Children.Add(outerStroke = new Rectangle()
            {
                Stroke = new SolidColorBrush(Colors.White),
                RadiusX = 0,
                RadiusY = 0,
                StrokeThickness = 2
            });

            this.Children.Add(new Rectangle()
            {
                Stroke = new SolidColorBrush(Colors.White),
                RadiusX = 3,
                RadiusY = 3,
                StrokeThickness = 2
            });

            whenBlock = new TextBlock()
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Top,
                TextWrapping = TextWrapping.Wrap,
                Foreground = new SolidColorBrush(Color.FromArgb(200, 255, 255, 255)),
                Text = "date string",
                Opacity = .25,
            };

            paneContentPanel.Children.Add(whenBlock);

            Speech speech = (Speech)item.Tag;
            string theDate = "";
            if (speech != null)
            {
                theDate = String.Format("{0:MMMM d, yyyy}", speech.When);
            }
            this.WhenLabel = theDate;

            textBlock.Text = item.Label;
            textBlockShadow.Text = item.Label;


        }

        public static readonly DependencyProperty WhenLabelProperty =
    DependencyProperty.RegisterAttached(
    "WhenLabel",
    typeof(string),
    typeof(TreeMapPanel), null
    );

        public string WhenLabel
        {
            get { return (string)GetValue(WhenLabelProperty); }
            set
            {
                SetValue(WhenLabelProperty, value);
                whenBlock.Text = value;

            }
        }

        public static string GetWhenLabel(DependencyObject element)
        {
            if (element == null) throw new NullReferenceException(element.ToString());

            return (string)element.GetValue(WhenLabelProperty);
        }

        public static void SetWhenLabel(DependencyObject element, string value)
        {
            if (element == null) throw new NullReferenceException(element.ToString());

            element.SetValue(WhenLabelProperty, value);
        }



    }

}
